/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. *//* JS symbol tables. */#include"vm/Shape-inl.h"#include"mozilla/DebugOnly.h"#include"mozilla/MathAlgorithms.h"#include"mozilla/PodOperations.h"#include"jsatom.h"#include"jscntxt.h"#include"jshashutil.h"#include"jsobj.h"#include"gc/Policy.h"#include"js/HashTable.h"#include"jscntxtinlines.h"#include"jscompartmentinlines.h"#include"jsobjinlines.h"#include"vm/Caches-inl.h"#include"vm/NativeObject-inl.h"usingnamespacejs;usingnamespacejs::gc;usingmozilla::CeilingLog2Size;usingmozilla::DebugOnly;usingmozilla::PodZero;usingmozilla::RotateLeft;usingJS::AutoCheckCannotGC;Shape*constShapeTable::Entry::SHAPE_REMOVED=(Shape*)ShapeTable::Entry::SHAPE_COLLISION;boolShapeTable::init(JSContext*cx,Shape*lastProp){uint32_tsizeLog2=CeilingLog2Size(entryCount_);uint32_tsize=JS_BIT(sizeLog2);if(entryCount_>=size-(size>>2))sizeLog2++;if(sizeLog2<MIN_SIZE_LOG2)sizeLog2=MIN_SIZE_LOG2;size=JS_BIT(sizeLog2);entries_=cx->pod_calloc<Entry>(size);if(!entries_)returnfalse;MOZ_ASSERT(sizeLog2<=HASH_BITS);hashShift_=HASH_BITS-sizeLog2;for(Shape::Range<NoGC>r(lastProp);!r.empty();r.popFront()){Shape&shape=r.front();Entry&entry=searchUnchecked<MaybeAdding::Adding>(shape.propid());/* * Beware duplicate args and arg vs. var conflicts: the youngest shape * (nearest to lastProp) must win. See bug 600067. */if(!entry.shape())entry.setPreservingCollision(&shape);}MOZ_ASSERT(capacity()==size);MOZ_ASSERT(size>=MIN_SIZE);MOZ_ASSERT(!needsToGrow());returntrue;}voidShape::removeFromDictionary(NativeObject*obj){MOZ_ASSERT(inDictionary());MOZ_ASSERT(obj->inDictionaryMode());MOZ_ASSERT(listp);MOZ_ASSERT(obj->shape_->inDictionary());MOZ_ASSERT(obj->shape_->listp==&obj->shape_);if(parent)parent->listp=listp;*listp=parent;listp=nullptr;obj->shape_->clearCachedBigEnoughForShapeTable();}voidShape::insertIntoDictionary(GCPtrShape*dictp){// Don't assert inDictionaryMode() here because we may be called from// JSObject::toDictionaryMode via JSObject::newDictionaryShape.MOZ_ASSERT(inDictionary());MOZ_ASSERT(!listp);MOZ_ASSERT_IF(*dictp,(*dictp)->inDictionary());MOZ_ASSERT_IF(*dictp,(*dictp)->listp==dictp);MOZ_ASSERT_IF(*dictp,zone()==(*dictp)->zone());setParent(dictp->get());if(parent)parent->listp=&parent;listp=(GCPtrShape*)dictp;*dictp=this;}boolShape::makeOwnBaseShape(JSContext*cx){MOZ_ASSERT(!base()->isOwned());MOZ_ASSERT(cx->zone()==zone());BaseShape*nbase=Allocate<BaseShape,NoGC>(cx);if(!nbase)returnfalse;new(nbase)BaseShape(StackBaseShape(this));nbase->setOwned(base()->toUnowned());this->base_=nbase;returntrue;}voidShape::handoffTableTo(Shape*shape){MOZ_ASSERT(inDictionary()&&shape->inDictionary());if(this==shape)return;MOZ_ASSERT(base()->isOwned()&&!shape->base()->isOwned());BaseShape*nbase=base();MOZ_ASSERT_IF(shape->hasSlot(),nbase->slotSpan()>shape->slot());this->base_=nbase->baseUnowned();nbase->adoptUnowned(shape->base()->toUnowned());shape->base_=nbase;}/* static */boolShape::hashify(JSContext*cx,Shape*shape){MOZ_ASSERT(!shape->hasTable());if(!shape->ensureOwnBaseShape(cx))returnfalse;ShapeTable*table=cx->new_<ShapeTable>(shape->entryCount());if(!table)returnfalse;if(!table->init(cx,shape)){js_free(table);returnfalse;}shape->base()->setTable(table);returntrue;}boolShapeTable::change(JSContext*cx,intlog2Delta){MOZ_ASSERT(entries_);MOZ_ASSERT(-1<=log2Delta&&log2Delta<=1);/* * Grow, shrink, or compress by changing this->entries_. */uint32_toldLog2=HASH_BITS-hashShift_;uint32_tnewLog2=oldLog2+log2Delta;uint32_toldSize=JS_BIT(oldLog2);uint32_tnewSize=JS_BIT(newLog2);Entry*newTable=cx->maybe_pod_calloc<Entry>(newSize);if(!newTable)returnfalse;/* Now that we have newTable allocated, update members. */MOZ_ASSERT(newLog2<=HASH_BITS);hashShift_=HASH_BITS-newLog2;removedCount_=0;Entry*oldTable=entries_;entries_=newTable;/* Copy only live entries, leaving removed and free ones behind. */AutoCheckCannotGCnogc;for(Entry*oldEntry=oldTable;oldSize!=0;oldEntry++){if(Shape*shape=oldEntry->shape()){Entry&entry=search<MaybeAdding::Adding>(shape->propid(),nogc);MOZ_ASSERT(entry.isFree());entry.setShape(shape);}oldSize--;}MOZ_ASSERT(capacity()==newSize);/* Finally, free the old entries storage. */js_free(oldTable);returntrue;}boolShapeTable::grow(JSContext*cx){MOZ_ASSERT(needsToGrow());uint32_tsize=capacity();intdelta=removedCount_<(size>>2);MOZ_ASSERT(entryCount_+removedCount_<=size-1);if(!change(cx,delta)){if(entryCount_+removedCount_==size-1){ReportOutOfMemory(cx);returnfalse;}}returntrue;}voidShapeTable::trace(JSTracer*trc){for(size_ti=0;i<capacity();i++){Entry&entry=getEntry(i);Shape*shape=entry.shape();if(shape){TraceManuallyBarrieredEdge(trc,&shape,"ShapeTable shape");if(shape!=entry.shape())entry.setPreservingCollision(shape);}}}#ifdef JSGC_HASH_TABLE_CHECKSvoidShapeTable::checkAfterMovingGC(){for(size_ti=0;i<capacity();i++){Entry&entry=getEntry(i);Shape*shape=entry.shape();if(shape)CheckGCThingAfterMovingGC(shape);}}#endif/* static */Shape*Shape::replaceLastProperty(JSContext*cx,StackBaseShape&base,TaggedProtoproto,HandleShapeshape){MOZ_ASSERT(!shape->inDictionary());if(!shape->parent){/* Treat as resetting the initial property of the shape hierarchy. */AllocKindkind=gc::GetGCObjectKind(shape->numFixedSlots());returnEmptyShape::getInitialShape(cx,base.clasp,proto,kind,base.flags&BaseShape::OBJECT_FLAG_MASK);}UnownedBaseShape*nbase=BaseShape::getUnowned(cx,base);if(!nbase)returnnullptr;Rooted<StackShape>child(cx,StackShape(shape));child.setBase(nbase);returncx->zone()->propertyTree().getChild(cx,shape->parent,child);}/* * Get or create a property-tree or dictionary child property of |parent|, * which must be lastProperty() if inDictionaryMode(), else parent must be * one of lastProperty() or lastProperty()->parent. *//* static */MOZ_ALWAYS_INLINEShape*NativeObject::getChildProperty(JSContext*cx,HandleNativeObjectobj,HandleShapeparent,MutableHandle<StackShape>child){/* * Shared properties have no slot, but slot_ will reflect that of parent. * Unshared properties allocate a slot here but may lose it due to a * JS_ClearScope call. */if(!child.hasSlot()){child.setSlot(parent->maybeSlot());}else{if(child.hasMissingSlot()){uint32_tslot;if(obj->inDictionaryMode()){if(!allocDictionarySlot(cx,obj,&slot))returnnullptr;}else{slot=obj->slotSpan();MOZ_ASSERT(slot>=JSSLOT_FREE(obj->getClass()));// Objects with many properties are converted to dictionary// mode, so we can't overflow SHAPE_MAXIMUM_SLOT here.MOZ_ASSERT(slot<JSSLOT_FREE(obj->getClass())+PropertyTree::MAX_HEIGHT);MOZ_ASSERT(slot<SHAPE_MAXIMUM_SLOT);}child.setSlot(slot);}else{/* * Slots can only be allocated out of order on objects in * dictionary mode. Otherwise the child's slot must be after the * parent's slot (if it has one), because slot number determines * slot span for objects with that shape. Usually child slot * *immediately* follows parent slot, but there may be a slot gap * when the object uses some -- but not all -- of its reserved * slots to store properties. */MOZ_ASSERT(obj->inDictionaryMode()||parent->hasMissingSlot()||child.slot()==parent->maybeSlot()+1||(parent->maybeSlot()+1<JSSLOT_FREE(obj->getClass())&&child.slot()==JSSLOT_FREE(obj->getClass())));}}if(obj->inDictionaryMode()){MOZ_ASSERT(parent==obj->lastProperty());Shape*shape=child.isAccessorShape()?Allocate<AccessorShape>(cx):Allocate<Shape>(cx);if(!shape)returnnullptr;if(child.hasSlot()&&child.slot()>=obj->lastProperty()->base()->slotSpan()){if(!obj->setSlotSpan(cx,child.slot()+1)){new(shape)Shape(obj->lastProperty()->base()->unowned(),0);returnnullptr;}}shape->initDictionaryShape(child,obj->numFixedSlots(),&obj->shape_);returnshape;}Shape*shape=cx->zone()->propertyTree().inlinedGetChild(cx,parent,child);if(!shape)returnnullptr;//MOZ_ASSERT(shape->parent == parent);//MOZ_ASSERT_IF(parent != lastProperty(), parent == lastProperty()->parent);if(!obj->setLastProperty(cx,shape))returnnullptr;returnshape;}/* static */booljs::NativeObject::toDictionaryMode(JSContext*cx,HandleNativeObjectobj){MOZ_ASSERT(!obj->inDictionaryMode());MOZ_ASSERT(cx->isInsideCurrentCompartment(obj));uint32_tspan=obj->slotSpan();// Clone the shapes into a new dictionary list. Don't update the last// property of this object until done, otherwise a GC triggered while// creating the dictionary will get the wrong slot span for this object.RootedShaperoot(cx);RootedShapedictionaryShape(cx);RootedShapeshape(cx,obj->lastProperty());while(shape){MOZ_ASSERT(!shape->inDictionary());Shape*dprop=shape->isAccessorShape()?Allocate<AccessorShape>(cx):Allocate<Shape>(cx);if(!dprop){ReportOutOfMemory(cx);returnfalse;}GCPtrShape*listp=dictionaryShape?&dictionaryShape->parent:nullptr;StackShapechild(shape);dprop->initDictionaryShape(child,obj->numFixedSlots(),listp);if(!dictionaryShape)root=dprop;MOZ_ASSERT(!dprop->hasTable());dictionaryShape=dprop;shape=shape->previous();}if(!Shape::hashify(cx,root)){ReportOutOfMemory(cx);returnfalse;}if(IsInsideNursery(obj)&&!cx->nursery().queueDictionaryModeObjectToSweep(obj)){ReportOutOfMemory(cx);returnfalse;}MOZ_ASSERT(root->listp==nullptr);root->listp=&obj->shape_;obj->shape_=root;MOZ_ASSERT(obj->inDictionaryMode());root->base()->setSlotSpan(span);returntrue;}staticboolShouldConvertToDictionary(NativeObject*obj){/* * Use a lower limit if this object is likely a hashmap (SETELEM was used * to set properties). */if(obj->hadElementsAccess())returnobj->lastProperty()->entryCount()>=PropertyTree::MAX_HEIGHT_WITH_ELEMENTS_ACCESS;returnobj->lastProperty()->entryCount()>=PropertyTree::MAX_HEIGHT;}staticMOZ_ALWAYS_INLINEUnownedBaseShape*GetBaseShapeForNewShape(JSContext*cx,HandleShapelast,HandleIdid){uint32_tindex;boolindexed=IdIsIndex(id,&index);boolinterestingSymbol=JSID_IS_SYMBOL(id)&&JSID_TO_SYMBOL(id)->isInterestingSymbol();if(MOZ_LIKELY(!indexed&&!interestingSymbol))returnlast->base()->unowned();StackBaseShapebase(last->base());if(indexed)base.flags|=BaseShape::INDEXED;elseif(interestingSymbol)base.flags|=BaseShape::HAS_INTERESTING_SYMBOL;returnBaseShape::getUnowned(cx,base);}/* static */Shape*NativeObject::addPropertyInternal(JSContext*cx,HandleNativeObjectobj,HandleIdid,GetterOpgetter,SetterOpsetter,uint32_tslot,unsignedattrs,unsignedflags,ShapeTable::Entry*entry,boolallowDictionary,constAutoKeepShapeTables&keep){MOZ_ASSERT_IF(!allowDictionary,!obj->inDictionaryMode());MOZ_ASSERT(getter!=JS_PropertyStub);MOZ_ASSERT(setter!=JS_StrictPropertyStub);AutoRooterGetterSettergsRoot(cx,attrs,&getter,&setter);/* * The code below deals with either converting obj to dictionary mode or * growing an object that's already in dictionary mode. Either way, * dictionray operations are safe if thread local. */ShapeTable*table=nullptr;if(!obj->inDictionaryMode()){boolstableSlot=(slot==SHAPE_INVALID_SLOT)||obj->lastProperty()->hasMissingSlot()||(slot==obj->lastProperty()->maybeSlot()+1);MOZ_ASSERT_IF(!allowDictionary,stableSlot);if(allowDictionary&&(!stableSlot||ShouldConvertToDictionary(obj))){if(!toDictionaryMode(cx,obj))returnnullptr;table=obj->lastProperty()->maybeTable(keep);entry=&table->search<MaybeAdding::Adding>(id,keep);}}else{table=obj->lastProperty()->ensureTableForDictionary(cx,keep);if(!table)returnnullptr;if(table->needsToGrow()){if(!table->grow(cx))returnnullptr;entry=&table->search<MaybeAdding::Adding>(id,keep);MOZ_ASSERT(!entry->shape());}}MOZ_ASSERT(!!table==!!entry);/* Find or create a property tree node labeled by our arguments. */RootedShapeshape(cx);{RootedShapelast(cx,obj->lastProperty());Rooted<UnownedBaseShape*>nbase(cx,GetBaseShapeForNewShape(cx,last,id));if(!nbase)returnnullptr;Rooted<StackShape>child(cx,StackShape(nbase,id,slot,attrs,flags));child.updateGetterSetter(getter,setter);shape=getChildProperty(cx,obj,last,&child);}if(shape){MOZ_ASSERT(shape==obj->lastProperty());if(table){/* Store the tree node pointer in the table entry for id. */entry->setPreservingCollision(shape);table->incEntryCount();/* Pass the table along to the new last property, namely shape. */MOZ_ASSERT(shape->parent->maybeTable(keep)==table);shape->parent->handoffTableTo(shape);}obj->checkShapeConsistency();returnshape;}obj->checkShapeConsistency();returnnullptr;}Shape*js::ReshapeForAllocKind(JSContext*cx,Shape*shape,TaggedProtoproto,gc::AllocKindallocKind){// Compute the number of fixed slots with the new allocation kind.size_tnfixed=gc::GetGCKindSlots(allocKind,shape->getObjectClass());// Get all the ids in the shape, in order.js::AutoIdVectorids(cx);{for(unsignedi=0;i<shape->slotSpan();i++){if(!ids.append(JSID_VOID))returnnullptr;}Shape*nshape=shape;while(!nshape->isEmptyShape()){ids[nshape->slot()].set(nshape->propid());nshape=nshape->previous();}}// Construct the new shape, without updating type information.RootedIdid(cx);RootedShapenewShape(cx,EmptyShape::getInitialShape(cx,shape->getObjectClass(),proto,nfixed,shape->getObjectFlags()));if(!newShape)returnnullptr;for(unsignedi=0;i<ids.length();i++){id=ids[i];Rooted<UnownedBaseShape*>nbase(cx,GetBaseShapeForNewShape(cx,newShape,id));if(!nbase)returnnullptr;Rooted<StackShape>child(cx,StackShape(nbase,id,i,JSPROP_ENUMERATE,0));newShape=cx->zone()->propertyTree().getChild(cx,newShape,child);if(!newShape)returnnullptr;}returnnewShape;}/* * Check and adjust the new attributes for the shape to make sure that our * slot access optimizations are sound. It is responsibility of the callers to * enforce all restrictions from ECMA-262 v5 8.12.9 [[DefineOwnProperty]]. */staticinlineboolCheckCanChangeAttrs(JSContext*cx,JSObject*obj,Shape*shape,unsigned*attrsp){if(shape->configurable())returntrue;/* A permanent property must stay permanent. */*attrsp|=JSPROP_PERMANENT;/* Reject attempts to remove a slot from the permanent data property. */if(shape->isDataDescriptor()&&shape->hasSlot()&&(*attrsp&(JSPROP_GETTER|JSPROP_SETTER|JSPROP_SHARED))){if(!cx->helperThread())JSObject::reportNotConfigurable(cx,shape->propid());returnfalse;}returntrue;}/* static */Shape*NativeObject::putProperty(JSContext*cx,HandleNativeObjectobj,HandleIdid,GetterOpgetter,SetterOpsetter,uint32_tslot,unsignedattrs,unsignedflags){MOZ_ASSERT(!JSID_IS_VOID(id));MOZ_ASSERT(getter!=JS_PropertyStub);MOZ_ASSERT(setter!=JS_StrictPropertyStub);#ifdef DEBUGif(obj->is<ArrayObject>()){ArrayObject*arr=&obj->as<ArrayObject>();uint32_tindex;if(IdIsIndex(id,&index))MOZ_ASSERT(index<arr->length()||arr->lengthIsWritable());}#endifAutoRooterGetterSettergsRoot(cx,attrs,&getter,&setter);/* * Search for id in order to claim its entry if table has been allocated. * * Note that we can only try to claim an entry in a table that is thread * local. An object may be thread local *without* its shape being thread * local. The only thread local objects that *also* have thread local * shapes are dictionaries that were allocated/converted thread * locally. Only for those objects we can try to claim an entry in its * shape table. */AutoKeepShapeTableskeep(cx);ShapeTable::Entry*entry;RootedShapeshape(cx);if(!Shape::search<MaybeAdding::Adding>(cx,obj->lastProperty(),id,keep,shape.address(),&entry)){returnnullptr;}if(!shape){/* * You can't add properties to a non-extensible object, but you can change * attributes of properties in such objects. */MOZ_ASSERT(obj->nonProxyIsExtensible());returnaddPropertyInternal(cx,obj,id,getter,setter,slot,attrs,flags,entry,true,keep);}/* Property exists: search must have returned a valid entry. */MOZ_ASSERT_IF(entry,!entry->isRemoved());if(!CheckCanChangeAttrs(cx,obj,shape,&attrs))returnnullptr;/* * If the caller wants to allocate a slot, but doesn't care which slot, * copy the existing shape's slot into slot so we can match shape, if all * other members match. */boolhadSlot=shape->hasSlot();uint32_toldSlot=shape->maybeSlot();if(!(attrs&JSPROP_SHARED)&&slot==SHAPE_INVALID_SLOT&&hadSlot)slot=oldSlot;Rooted<UnownedBaseShape*>nbase(cx);{RootedShapeshape(cx,obj->lastProperty());nbase=GetBaseShapeForNewShape(cx,shape,id);if(!nbase)returnnullptr;}/* * Now that we've possibly preserved slot, check whether all members match. * If so, this is a redundant "put" and we can return without more work. */if(shape->matchesParamsAfterId(nbase,slot,attrs,flags,getter,setter))returnshape;/* * Overwriting a non-last property requires switching to dictionary mode. * The shape tree is shared immutable, and we can't removeProperty and then * addPropertyInternal because a failure under add would lose data. */if(shape!=obj->lastProperty()&&!obj->inDictionaryMode()){if(!toDictionaryMode(cx,obj))returnnullptr;ShapeTable*table=obj->lastProperty()->maybeTable(keep);MOZ_ASSERT(table);entry=&table->search<MaybeAdding::NotAdding>(shape->propid(),keep);shape=entry->shape();}MOZ_ASSERT_IF(shape->hasSlot()&&!(attrs&JSPROP_SHARED),shape->slot()==slot);if(obj->inDictionaryMode()){/* * Updating some property in a dictionary-mode object. Create a new * shape for the existing property, and also generate a new shape for * the last property of the dictionary (unless the modified property * is also the last property). */boolupdateLast=(shape==obj->lastProperty());boolaccessorShape=getter||setter||(attrs&(JSPROP_GETTER|JSPROP_SETTER));shape=NativeObject::replaceWithNewEquivalentShape(cx,obj,shape,nullptr,accessorShape);if(!shape)returnnullptr;if(!updateLast&&!NativeObject::generateOwnShape(cx,obj))returnnullptr;/* * FIXME bug 593129 -- slot allocation and NativeObject *this must move * out of here! */if(slot==SHAPE_INVALID_SLOT&&!(attrs&JSPROP_SHARED)){if(!allocDictionarySlot(cx,obj,&slot))returnnullptr;}if(updateLast)shape->base()->adoptUnowned(nbase);elseshape->base_=nbase;MOZ_ASSERT_IF(attrs&(JSPROP_GETTER|JSPROP_SETTER),attrs&JSPROP_SHARED);shape->setSlot(slot);shape->attrs=uint8_t(attrs);shape->flags=flags|Shape::IN_DICTIONARY|(accessorShape?Shape::ACCESSOR_SHAPE:0);if(shape->isAccessorShape()){AccessorShape&accShape=shape->asAccessorShape();accShape.rawGetter=getter;accShape.rawSetter=setter;GetterSetterWriteBarrierPost(&accShape);}else{MOZ_ASSERT(!getter);MOZ_ASSERT(!setter);}}else{/* * Updating the last property in a non-dictionary-mode object. Find an * alternate shared child of the last property's previous shape. */StackBaseShapebase(obj->lastProperty()->base());UnownedBaseShape*nbase=BaseShape::getUnowned(cx,base);if(!nbase)returnnullptr;MOZ_ASSERT(shape==obj->lastProperty());/* Find or create a property tree node labeled by our arguments. */Rooted<StackShape>child(cx,StackShape(nbase,id,slot,attrs,flags));child.updateGetterSetter(getter,setter);RootedShapeparent(cx,shape->parent);Shape*newShape=getChildProperty(cx,obj,parent,&child);if(!newShape){obj->checkShapeConsistency();returnnullptr;}shape=newShape;}/* * Can't fail now, so free the previous incarnation's slot if the new shape * has no slot. But we do not need to free oldSlot (and must not, as trying * to will botch an assertion in JSObject::freeSlot) if the new last * property (shape here) has a slotSpan that does not cover it. */if(hadSlot&&!shape->hasSlot()){if(oldSlot<obj->slotSpan())obj->freeSlot(cx,oldSlot);/* Note: The optimization based on propertyRemovals is only relevant to the active thread. */if(!cx->helperThread())++cx->propertyRemovals;}obj->checkShapeConsistency();returnshape;}/* static */Shape*NativeObject::changeProperty(JSContext*cx,HandleNativeObjectobj,HandleShapeshape,unsignedattrs,GetterOpgetter,SetterOpsetter){MOZ_ASSERT(obj->containsPure(shape));MOZ_ASSERT(getter!=JS_PropertyStub);MOZ_ASSERT(setter!=JS_StrictPropertyStub);MOZ_ASSERT_IF(attrs&(JSPROP_GETTER|JSPROP_SETTER),attrs&JSPROP_SHARED);/* Allow only shared (slotless) => unshared (slotful) transition. */MOZ_ASSERT(!((attrs^shape->attrs)&JSPROP_SHARED)||!(attrs&JSPROP_SHARED));MarkTypePropertyNonData(cx,obj,shape->propid());if(!CheckCanChangeAttrs(cx,obj,shape,&attrs))returnnullptr;if(shape->attrs==attrs&&shape->getter()==getter&&shape->setter()==setter)returnshape;/* * Let JSObject::putProperty handle this |overwriting| case, including * the conservation of shape->slot (if it's valid). We must not call * removeProperty because it will free an allocated shape->slot, and * putProperty won't re-allocate it. */RootedIdpropid(cx,shape->propid());Shape*newShape=putProperty(cx,obj,propid,getter,setter,shape->maybeSlot(),attrs,shape->flags);obj->checkShapeConsistency();returnnewShape;}/* static */boolNativeObject::removeProperty(JSContext*cx,HandleNativeObjectobj,jsidid_){RootedIdid(cx,id_);AutoKeepShapeTableskeep(cx);ShapeTable::Entry*entry;RootedShapeshape(cx);if(!Shape::search(cx,obj->lastProperty(),id,keep,shape.address(),&entry))returnfalse;if(!shape)returntrue;/* * If shape is not the last property added, or the last property cannot * be removed, switch to dictionary mode. */if(!obj->inDictionaryMode()&&(shape!=obj->lastProperty()||!obj->canRemoveLastProperty())){if(!toDictionaryMode(cx,obj))returnfalse;ShapeTable*table=obj->lastProperty()->maybeTable(keep);MOZ_ASSERT(table);entry=&table->search<MaybeAdding::NotAdding>(shape->propid(),keep);shape=entry->shape();}/* * If in dictionary mode, get a new shape for the last property after the * removal. We need a fresh shape for all dictionary deletions, even of * the last property. Otherwise, a shape could replay and caches might * return deleted DictionaryShapes! See bug 595365. Do this before changing * the object or table, so the remaining removal is infallible. */RootedShapespare(cx);if(obj->inDictionaryMode()){/* For simplicity, always allocate an accessor shape for now. */spare=Allocate<AccessorShape>(cx);if(!spare)returnfalse;new(spare)Shape(shape->base()->unowned(),0);if(shape==obj->lastProperty()){/* * Get an up to date unowned base shape for the new last property * when removing the dictionary's last property. Information in * base shapes for non-last properties may be out of sync with the * object's state. */RootedShapeprevious(cx,obj->lastProperty()->parent);StackBaseShapebase(obj->lastProperty()->base());BaseShape*nbase=BaseShape::getUnowned(cx,base);if(!nbase)returnfalse;previous->base_=nbase;}}/* If shape has a slot, free its slot number. */if(shape->hasSlot()){obj->freeSlot(cx,shape->slot());if(!cx->helperThread())++cx->propertyRemovals;}/* * A dictionary-mode object owns mutable, unique shapes on a non-circular * doubly linked list, hashed by lastProperty()->table. So we can edit the * list and hash in place. */if(obj->inDictionaryMode()){ShapeTable*table=obj->lastProperty()->maybeTable(keep);MOZ_ASSERT(table);if(entry->hadCollision()){entry->setRemoved();table->decEntryCount();table->incRemovedCount();}else{entry->setFree();table->decEntryCount();#ifdef DEBUG/* * Check the consistency of the table but limit the number of * checks not to alter significantly the complexity of the * delete in debug builds, see bug 534493. */Shape*aprop=obj->lastProperty();for(intn=50;--n>=0&&aprop->parent;aprop=aprop->parent)MOZ_ASSERT_IF(aprop!=shape,obj->contains(cx,aprop));#endif}{/* Remove shape from its non-circular doubly linked list. */Shape*oldLastProp=obj->lastProperty();shape->removeFromDictionary(obj);/* Hand off table from the old to new last property. */oldLastProp->handoffTableTo(obj->lastProperty());}/* Generate a new shape for the object, infallibly. */JS_ALWAYS_TRUE(NativeObject::generateOwnShape(cx,obj,spare));/* Consider shrinking table if its load factor is <= .25. */uint32_tsize=table->capacity();if(size>ShapeTable::MIN_SIZE&&table->entryCount()<=size>>2)(void)table->change(cx,-1);}else{/* * Non-dictionary-mode shape tables are shared immutables, so all we * need do is retract the last property and we'll either get or else * lazily make via a later hashify the exact table for the new property * lineage. */MOZ_ASSERT(shape==obj->lastProperty());obj->removeLastProperty(cx);}obj->checkShapeConsistency();returntrue;}/* static */voidNativeObject::clear(JSContext*cx,HandleNativeObjectobj){Shape*shape=obj->lastProperty();MOZ_ASSERT(obj->inDictionaryMode()==shape->inDictionary());while(shape->parent){shape=shape->parent;MOZ_ASSERT(obj->inDictionaryMode()==shape->inDictionary());}MOZ_ASSERT(shape->isEmptyShape());if(obj->inDictionaryMode())shape->listp=&obj->shape_;JS_ALWAYS_TRUE(obj->setLastProperty(cx,shape));if(!cx->helperThread())++cx->propertyRemovals;obj->checkShapeConsistency();}/* static */boolNativeObject::rollbackProperties(JSContext*cx,HandleNativeObjectobj,uint32_tslotSpan){/* * Remove properties from this object until it has a matching slot span. * The object cannot have escaped in a way which would prevent safe * removal of the last properties. */MOZ_ASSERT(!obj->inDictionaryMode()&&slotSpan<=obj->slotSpan());while(true){if(obj->lastProperty()->isEmptyShape()){MOZ_ASSERT(slotSpan==0);break;}else{uint32_tslot=obj->lastProperty()->slot();if(slot<slotSpan)break;}if(!NativeObject::removeProperty(cx,obj,obj->lastProperty()->propid()))returnfalse;}returntrue;}/* static */Shape*NativeObject::replaceWithNewEquivalentShape(JSContext*cx,HandleNativeObjectobj,Shape*oldShape,Shape*newShape,boolaccessorShape){MOZ_ASSERT(cx->isInsideCurrentZone(oldShape));MOZ_ASSERT_IF(oldShape!=obj->lastProperty(),obj->inDictionaryMode()&&obj->lookup(cx,oldShape->propidRef())==oldShape);if(!obj->inDictionaryMode()){RootedShapenewRoot(cx,newShape);if(!toDictionaryMode(cx,obj))returnnullptr;oldShape=obj->lastProperty();newShape=newRoot;}if(!newShape){RootedShapeoldRoot(cx,oldShape);newShape=(oldShape->isAccessorShape()||accessorShape)?Allocate<AccessorShape>(cx):Allocate<Shape>(cx);if(!newShape)returnnullptr;new(newShape)Shape(oldRoot->base()->unowned(),0);oldShape=oldRoot;}AutoCheckCannotGCnogc;ShapeTable*table=obj->lastProperty()->ensureTableForDictionary(cx,nogc);if(!table)returnnullptr;ShapeTable::Entry*entry=oldShape->isEmptyShape()?nullptr:&table->search<MaybeAdding::NotAdding>(oldShape->propidRef(),nogc);/* * Splice the new shape into the same position as the old shape, preserving * enumeration order (see bug 601399). */StackShapenshape(oldShape);newShape->initDictionaryShape(nshape,obj->numFixedSlots(),oldShape->listp);MOZ_ASSERT(newShape->parent==oldShape);oldShape->removeFromDictionary(obj);if(newShape==obj->lastProperty())oldShape->handoffTableTo(newShape);if(entry)entry->setPreservingCollision(newShape);returnnewShape;}/* static */boolNativeObject::shadowingShapeChange(JSContext*cx,HandleNativeObjectobj,constShape&shape){returngenerateOwnShape(cx,obj);}/* static */boolJSObject::setFlags(JSContext*cx,HandleObjectobj,BaseShape::Flagflags,GenerateShapegenerateShape){if(obj->hasAllFlags(flags))returntrue;if(obj->isNative()&&obj->as<NativeObject>().inDictionaryMode()){if(generateShape==GENERATE_SHAPE){if(!NativeObject::generateOwnShape(cx,obj.as<NativeObject>()))returnfalse;}StackBaseShapebase(obj->as<NativeObject>().lastProperty());base.flags|=flags;UnownedBaseShape*nbase=BaseShape::getUnowned(cx,base);if(!nbase)returnfalse;obj->as<NativeObject>().lastProperty()->base()->adoptUnowned(nbase);returntrue;}Shape*existingShape=obj->ensureShape(cx);if(!existingShape)returnfalse;Shape*newShape=Shape::setObjectFlags(cx,flags,obj->taggedProto(),existingShape);if(!newShape)returnfalse;// The success of the |JSObject::ensureShape| call above means that |obj|// can be assumed to have a shape.obj->as<ShapedObject>().setShape(newShape);returntrue;}/* static */boolNativeObject::clearFlag(JSContext*cx,HandleNativeObjectobj,BaseShape::Flagflag){MOZ_ASSERT(obj->inDictionaryMode());MOZ_ASSERT(obj->lastProperty()->getObjectFlags()&flag);StackBaseShapebase(obj->lastProperty());base.flags&=~flag;UnownedBaseShape*nbase=BaseShape::getUnowned(cx,base);if(!nbase)returnfalse;obj->lastProperty()->base()->adoptUnowned(nbase);returntrue;}/* static */Shape*Shape::setObjectFlags(JSContext*cx,BaseShape::Flagflags,TaggedProtoproto,Shape*last){if((last->getObjectFlags()&flags)==flags)returnlast;StackBaseShapebase(last);base.flags|=flags;RootedShapelastRoot(cx,last);returnreplaceLastProperty(cx,base,proto,lastRoot);}/* static */inlineHashNumberStackBaseShape::hash(constLookup&lookup){HashNumberhash=lookup.flags;hash=RotateLeft(hash,4)^(uintptr_t(lookup.clasp)>>3);returnhash;}/* static */inlineboolStackBaseShape::match(constReadBarriered<UnownedBaseShape*>&key,constLookup&lookup){returnkey.unbarrieredGet()->flags==lookup.flags&&key.unbarrieredGet()->clasp_==lookup.clasp;}inlineBaseShape::BaseShape(constStackBaseShape&base):clasp_(base.clasp),flags(base.flags),slotSpan_(0),unowned_(nullptr),table_(nullptr){}/* static */voidBaseShape::copyFromUnowned(BaseShape&dest,UnownedBaseShape&src){dest.clasp_=src.clasp_;dest.slotSpan_=src.slotSpan_;dest.unowned_=&src;dest.flags=src.flags|OWNED_SHAPE;}inlinevoidBaseShape::adoptUnowned(UnownedBaseShape*other){// This is a base shape owned by a dictionary object, update it to reflect the// unowned base shape of a new last property.MOZ_ASSERT(isOwned());uint32_tspan=slotSpan();BaseShape::copyFromUnowned(*this,*other);setSlotSpan(span);assertConsistency();}/* static */UnownedBaseShape*BaseShape::getUnowned(JSContext*cx,StackBaseShape&base){auto&table=cx->zone()->baseShapes();if(!table.initialized()&&!table.init()){ReportOutOfMemory(cx);returnnullptr;}autop=MakeDependentAddPtr(cx,table,base);if(p)return*p;BaseShape*nbase_=Allocate<BaseShape>(cx);if(!nbase_)returnnullptr;new(nbase_)BaseShape(base);UnownedBaseShape*nbase=static_cast<UnownedBaseShape*>(nbase_);if(!p.add(cx,table,base,nbase))returnnullptr;returnnbase;}voidBaseShape::assertConsistency(){#ifdef DEBUGif(isOwned()){UnownedBaseShape*unowned=baseUnowned();MOZ_ASSERT(getObjectFlags()==unowned->getObjectFlags());}#endif}voidBaseShape::traceChildren(JSTracer*trc){traceChildrenSkipShapeTable(trc);traceShapeTable(trc);}voidBaseShape::traceChildrenSkipShapeTable(JSTracer*trc){if(isOwned())TraceEdge(trc,&unowned_,"base");assertConsistency();}voidBaseShape::traceShapeTable(JSTracer*trc){AutoCheckCannotGCnogc;if(ShapeTable*table=maybeTable(nogc))table->trace(trc);}#ifdef DEBUGboolBaseShape::canSkipMarkingShapeTable(Shape*lastShape){// Check that every shape in the shape table will be marked by marking// |lastShape|.AutoCheckCannotGCnogc;ShapeTable*table=maybeTable(nogc);if(!table)returntrue;uint32_tcount=0;for(Shape::Range<NoGC>r(lastShape);!r.empty();r.popFront()){Shape*shape=&r.front();ShapeTable::Entry&entry=table->search<MaybeAdding::NotAdding>(shape->propid(),nogc);if(entry.isLive())count++;}returncount==table->entryCount();}#endif#ifdef JSGC_HASH_TABLE_CHECKSvoidZone::checkBaseShapeTableAfterMovingGC(){if(!baseShapes().initialized())return;for(autor=baseShapes().all();!r.empty();r.popFront()){UnownedBaseShape*base=r.front().unbarrieredGet();CheckGCThingAfterMovingGC(base);BaseShapeSet::Ptrptr=baseShapes().lookup(base);MOZ_RELEASE_ASSERT(ptr.found()&&&*ptr==&r.front());}}#endif // JSGC_HASH_TABLE_CHECKSvoidBaseShape::finalize(FreeOp*fop){if(table_){fop->delete_(table_);table_=nullptr;}}inlineInitialShapeEntry::InitialShapeEntry():shape(nullptr),proto(){}inlineInitialShapeEntry::InitialShapeEntry(Shape*shape,constLookup::ShapeProto&proto):shape(shape),proto(proto){}/* static */inlineHashNumberInitialShapeEntry::hash(constLookup&lookup){return(RotateLeft(uintptr_t(lookup.clasp)>>3,4)^lookup.proto.hashCode())+lookup.nfixed;}/* static */inlineboolInitialShapeEntry::match(constInitialShapeEntry&key,constLookup&lookup){constShape*shape=key.shape.unbarrieredGet();returnlookup.clasp==shape->getObjectClass()&&lookup.nfixed==shape->numFixedSlots()&&lookup.baseFlags==shape->getObjectFlags()&&lookup.proto.match(key.proto);}#ifdef JSGC_HASH_TABLE_CHECKSvoidZone::checkInitialShapesTableAfterMovingGC(){if(!initialShapes().initialized())return;/* * Assert that the postbarriers have worked and that nothing is left in * initialShapes that points into the nursery, and that the hash table * entries are discoverable. */for(autor=initialShapes().all();!r.empty();r.popFront()){InitialShapeEntryentry=r.front();JSProtoKeyprotoKey=entry.proto.key();TaggedProtoproto=entry.proto.proto().unbarrieredGet();Shape*shape=entry.shape.unbarrieredGet();CheckGCThingAfterMovingGC(shape);if(proto.isObject())CheckGCThingAfterMovingGC(proto.toObject());usingLookup=InitialShapeEntry::Lookup;Lookuplookup(shape->getObjectClass(),Lookup::ShapeProto(protoKey,proto),shape->numFixedSlots(),shape->getObjectFlags());InitialShapeSet::Ptrptr=initialShapes().lookup(lookup);MOZ_RELEASE_ASSERT(ptr.found()&&&*ptr==&r.front());}}#endif // JSGC_HASH_TABLE_CHECKSShape*EmptyShape::new_(JSContext*cx,Handle<UnownedBaseShape*>base,uint32_tnfixed){Shape*shape=Allocate<Shape>(cx);if(!shape){ReportOutOfMemory(cx);returnnullptr;}new(shape)EmptyShape(base,nfixed);returnshape;}MOZ_ALWAYS_INLINEHashNumberShapeHasher::hash(constLookup&l){returnl.hash();}MOZ_ALWAYS_INLINEboolShapeHasher::match(constKeyk,constLookup&l){returnk->matches(l);}staticKidsHash*HashChildren(Shape*kid1,Shape*kid2){KidsHash*hash=js_new<KidsHash>();if(!hash||!hash->init(2)){js_delete(hash);returnnullptr;}hash->putNewInfallible(StackShape(kid1),kid1);hash->putNewInfallible(StackShape(kid2),kid2);returnhash;}boolPropertyTree::insertChild(JSContext*cx,Shape*parent,Shape*child){MOZ_ASSERT(!parent->inDictionary());MOZ_ASSERT(!child->parent);MOZ_ASSERT(!child->inDictionary());MOZ_ASSERT(child->zone()==parent->zone());MOZ_ASSERT(cx->zone()==zone_);KidsPointer*kidp=&parent->kids;if(kidp->isNull()){child->setParent(parent);kidp->setShape(child);returntrue;}if(kidp->isShape()){Shape*shape=kidp->toShape();MOZ_ASSERT(shape!=child);MOZ_ASSERT(!shape->matches(child));KidsHash*hash=HashChildren(shape,child);if(!hash){ReportOutOfMemory(cx);returnfalse;}kidp->setHash(hash);child->setParent(parent);returntrue;}if(!kidp->toHash()->putNew(StackShape(child),child)){ReportOutOfMemory(cx);returnfalse;}child->setParent(parent);returntrue;}voidShape::removeChild(Shape*child){MOZ_ASSERT(!child->inDictionary());MOZ_ASSERT(child->parent==this);KidsPointer*kidp=&kids;if(kidp->isShape()){MOZ_ASSERT(kidp->toShape()==child);kidp->setNull();child->parent=nullptr;return;}KidsHash*hash=kidp->toHash();MOZ_ASSERT(hash->count()>=2);/* otherwise kidp->isShape() should be true */#ifdef DEBUGsize_toldCount=hash->count();#endifhash->remove(StackShape(child));child->parent=nullptr;MOZ_ASSERT(hash->count()==oldCount-1);if(hash->count()==1){/* Convert from HASH form back to SHAPE form. */KidsHash::Ranger=hash->all();Shape*otherChild=r.front();MOZ_ASSERT((r.popFront(),r.empty()));/* No more elements! */kidp->setShape(otherChild);js_delete(hash);}}MOZ_ALWAYS_INLINEShape*PropertyTree::inlinedGetChild(JSContext*cx,Shape*parent,Handle<StackShape>child){MOZ_ASSERT(parent);Shape*existingShape=nullptr;/* * The property tree has extremely low fan-out below its root in * popular embeddings with real-world workloads. Patterns such as * defining closures that capture a constructor's environment as * getters or setters on the new object that is passed in as * |this| can significantly increase fan-out below the property * tree root -- see bug 335700 for details. */KidsPointer*kidp=&parent->kids;if(kidp->isShape()){Shape*kid=kidp->toShape();if(kid->matches(child))existingShape=kid;}elseif(kidp->isHash()){if(KidsHash::Ptrp=kidp->toHash()->lookup(child))existingShape=*p;}else{/* If kidp->isNull(), we always insert. */}if(existingShape){JS::Zone*zone=existingShape->zone();if(zone->needsIncrementalBarrier()){/* * We need a read barrier for the shape tree, since these are weak * pointers. */Shape*tmp=existingShape;TraceManuallyBarrieredEdge(zone->barrierTracer(),&tmp,"read barrier");MOZ_ASSERT(tmp==existingShape);returnexistingShape;}if(!zone->isGCSweepingOrCompacting()||!IsAboutToBeFinalizedUnbarriered(&existingShape)){if(existingShape->isMarked(gc::GRAY))UnmarkGrayShapeRecursively(existingShape);returnexistingShape;}/* * The shape we've found is unreachable and due to be finalized, so * remove our weak reference to it and don't use it. */MOZ_ASSERT(parent->isMarked());parent->removeChild(existingShape);}RootedShapeparentRoot(cx,parent);Shape*shape=Shape::new_(cx,child,parentRoot->numFixedSlots());if(!shape)returnnullptr;if(!insertChild(cx,parentRoot,shape))returnnullptr;returnshape;}Shape*PropertyTree::getChild(JSContext*cx,Shape*parent,Handle<StackShape>child){returninlinedGetChild(cx,parent,child);}voidShape::sweep(){/* * We detach the child from the parent if the parent is reachable. * * This test depends on shape arenas not being freed until after we finish * incrementally sweeping them. If that were not the case the parent pointer * could point to a marked cell that had been deallocated and then * reallocated, since allocating a cell in a zone that is being marked will * set the mark bit for that cell. */if(parent&&parent->isMarked()){if(inDictionary()){if(parent->listp==&parent)parent->listp=nullptr;}else{parent->removeChild(this);}}}voidShape::finalize(FreeOp*fop){if(!inDictionary()&&kids.isHash())fop->delete_(kids.toHash());}voidShape::fixupDictionaryShapeAfterMovingGC(){if(!listp)return;// The listp field either points to the parent field of the next shape in// the list if there is one. Otherwise if this shape is the last in the// list then it points to the shape_ field of the object the list is for.// We can tell which it is because the base shape is owned if this is the// last property and not otherwise.boollistpPointsIntoShape=!MaybeForwarded(base())->isOwned();#ifdef DEBUG// Check that we got this right by interrogating the arena.// We use a fake cell pointer for this: it might not point to the beginning// of a cell, but will point into the right arena and will have the right// alignment.Cell*cell=reinterpret_cast<Cell*>(uintptr_t(listp)&~CellAlignMask);AllocKindkind=TenuredCell::fromPointer(cell)->getAllocKind();MOZ_ASSERT_IF(listpPointsIntoShape,IsShapeAllocKind(kind));MOZ_ASSERT_IF(!listpPointsIntoShape,IsObjectAllocKind(kind));#endifif(listpPointsIntoShape){// listp points to the parent field of the next shape.Shape*next=reinterpret_cast<Shape*>(uintptr_t(listp)-offsetof(Shape,parent));if(gc::IsForwarded(next))listp=&gc::Forwarded(next)->parent;}else{// listp points to the shape_ field of an object.JSObject*last=reinterpret_cast<JSObject*>(uintptr_t(listp)-ShapedObject::offsetOfShape());if(gc::IsForwarded(last))listp=&gc::Forwarded(last)->as<NativeObject>().shape_;}}voidShape::fixupShapeTreeAfterMovingGC(){if(kids.isNull())return;if(kids.isShape()){if(gc::IsForwarded(kids.toShape()))kids.setShape(gc::Forwarded(kids.toShape()));return;}MOZ_ASSERT(kids.isHash());KidsHash*kh=kids.toHash();for(KidsHash::Enume(*kh);!e.empty();e.popFront()){Shape*key=e.front();if(IsForwarded(key))key=Forwarded(key);BaseShape*base=key->base();if(IsForwarded(base))base=Forwarded(base);UnownedBaseShape*unowned=base->unowned();if(IsForwarded(unowned))unowned=Forwarded(unowned);GetterOpgetter=key->getter();if(key->hasGetterObject())getter=GetterOp(MaybeForwarded(key->getterObject()));SetterOpsetter=key->setter();if(key->hasSetterObject())setter=SetterOp(MaybeForwarded(key->setterObject()));StackShapelookup(unowned,const_cast<Shape*>(key)->propidRef(),key->slotInfo&Shape::SLOT_MASK,key->attrs,key->flags);lookup.updateGetterSetter(getter,setter);e.rekeyFront(lookup,key);}}voidShape::fixupAfterMovingGC(){if(inDictionary())fixupDictionaryShapeAfterMovingGC();elsefixupShapeTreeAfterMovingGC();}voidShape::fixupGetterSetterForBarrier(JSTracer*trc){if(!hasGetterValue()&&!hasSetterValue())return;JSObject*priorGetter=asAccessorShape().getterObj;JSObject*priorSetter=asAccessorShape().setterObj;if(!priorGetter&&!priorSetter)return;JSObject*postGetter=priorGetter;JSObject*postSetter=priorSetter;if(priorGetter)TraceManuallyBarrieredEdge(trc,&postGetter,"getterObj");if(priorSetter)TraceManuallyBarrieredEdge(trc,&postSetter,"setterObj");if(priorGetter==postGetter&&priorSetter==postSetter)return;if(parent&&!parent->inDictionary()&&parent->kids.isHash()){// Relocating the getterObj or setterObj will have changed our location// in our parent's KidsHash, so take care to update it. We must do this// before we update the shape itself, since the shape is used to match// the original entry in the hash set.StackShapeoriginal(this);StackShapeupdated(this);updated.rawGetter=reinterpret_cast<GetterOp>(postGetter);updated.rawSetter=reinterpret_cast<SetterOp>(postSetter);KidsHash*kh=parent->kids.toHash();MOZ_ALWAYS_TRUE(kh->rekeyAs(original,updated,this));}asAccessorShape().getterObj=postGetter;asAccessorShape().setterObj=postSetter;MOZ_ASSERT_IF(parent&&!parent->inDictionary()&&parent->kids.isHash(),parent->kids.toHash()->has(StackShape(this)));}#ifdef DEBUGvoidKidsPointer::checkConsistency(Shape*aKid)const{if(isShape()){MOZ_ASSERT(toShape()==aKid);}else{MOZ_ASSERT(isHash());KidsHash*hash=toHash();KidsHash::Ptrptr=hash->lookup(StackShape(aKid));MOZ_ASSERT(*ptr==aKid);}}voidShape::dump(FILE*fp)const{jsidpropid=this->propid();MOZ_ASSERT(!JSID_IS_VOID(propid));if(JSID_IS_INT(propid)){fprintf(fp,"[%ld]",(long)JSID_TO_INT(propid));}elseif(JSID_IS_ATOM(propid)){if(JSLinearString*str=JSID_TO_ATOM(propid))FileEscapedString(fp,str,'"');elsefputs("<error>",fp);}else{MOZ_ASSERT(JSID_IS_SYMBOL(propid));JSID_TO_SYMBOL(propid)->dump(fp);}fprintf(fp," g/s %p/%p slot %d attrs %x ",JS_FUNC_TO_DATA_PTR(void*,getter()),JS_FUNC_TO_DATA_PTR(void*,setter()),hasSlot()?slot():-1,attrs);if(attrs){intfirst=1;fputs("(",fp);#define DUMP_ATTR(name, display) if (attrs & JSPROP_##name) fputs(&(" " #display)[first], fp), first = 0DUMP_ATTR(ENUMERATE,enumerate);DUMP_ATTR(READONLY,readonly);DUMP_ATTR(PERMANENT,permanent);DUMP_ATTR(GETTER,getter);DUMP_ATTR(SETTER,setter);DUMP_ATTR(SHARED,shared);#undef DUMP_ATTRfputs(") ",fp);}fprintf(fp,"flags %x ",flags);if(flags){intfirst=1;fputs("(",fp);#define DUMP_FLAG(name, display) if (flags & name) fputs(&(" " #display)[first], fp), first = 0DUMP_FLAG(IN_DICTIONARY,in_dictionary);#undef DUMP_FLAGfputs(") ",fp);}}voidShape::dumpSubtree(intlevel,FILE*fp)const{if(!parent){MOZ_ASSERT(level==0);MOZ_ASSERT(JSID_IS_EMPTY(propid_));fprintf(fp,"class %s emptyShape\n",getObjectClass()->name);}else{fprintf(fp,"%*sid ",level,"");dump(fp);}if(!kids.isNull()){++level;if(kids.isShape()){Shape*kid=kids.toShape();MOZ_ASSERT(kid->parent==this);kid->dumpSubtree(level,fp);}else{constKidsHash&hash=*kids.toHash();for(KidsHash::Rangerange=hash.all();!range.empty();range.popFront()){Shape*kid=range.front();MOZ_ASSERT(kid->parent==this);kid->dumpSubtree(level,fp);}}}}#endifstaticboolIsOriginalProto(GlobalObject*global,JSProtoKeykey,JSObject&proto){if(global->getPrototype(key)!=ObjectValue(proto))returnfalse;if(key==JSProto_Object){MOZ_ASSERT(proto.staticPrototypeIsImmutable(),"proto should be Object.prototype, whose prototype is ""immutable");MOZ_ASSERT(proto.staticPrototype()==nullptr,"Object.prototype must have null prototype");returntrue;}// Check that other prototypes still have Object.prototype as proto.JSObject*protoProto=proto.staticPrototype();if(!protoProto||global->getPrototype(JSProto_Object)!=ObjectValue(*protoProto))returnfalse;MOZ_ASSERT(protoProto->staticPrototypeIsImmutable(),"protoProto should be Object.prototype, whose prototype is ""immutable");MOZ_ASSERT(protoProto->staticPrototype()==nullptr,"Object.prototype must have null prototype");returntrue;}staticJSProtoKeyGetInitialShapeProtoKey(TaggedProtoproto,JSContext*cx){if(proto.isObject()&&proto.toObject()->hasStaticPrototype()){GlobalObject*global=cx->global();JSObject&obj=*proto.toObject();MOZ_ASSERT(global==&obj.global());if(IsOriginalProto(global,JSProto_Object,obj))returnJSProto_Object;if(IsOriginalProto(global,JSProto_Function,obj))returnJSProto_Function;if(IsOriginalProto(global,JSProto_Array,obj))returnJSProto_Array;if(IsOriginalProto(global,JSProto_RegExp,obj))returnJSProto_RegExp;}returnJSProto_LIMIT;}/* static */Shape*EmptyShape::getInitialShape(JSContext*cx,constClass*clasp,TaggedProtoproto,size_tnfixed,uint32_tobjectFlags){MOZ_ASSERT_IF(proto.isObject(),cx->isInsideCurrentCompartment(proto.toObject()));auto&table=cx->zone()->initialShapes();if(!table.initialized()&&!table.init()){ReportOutOfMemory(cx);returnnullptr;}usingLookup=InitialShapeEntry::Lookup;autoprotoPointer=MakeDependentAddPtr(cx,table,Lookup(clasp,Lookup::ShapeProto(proto),nfixed,objectFlags));if(protoPointer)returnprotoPointer->shape;// No entry for this proto. If the proto is one of a few common builtin// prototypes, try to do a lookup based on the JSProtoKey, so we can share// shapes across globals.Rooted<TaggedProto>protoRoot(cx,proto);Shape*shape=nullptr;boolinsertKey=false;mozilla::Maybe<DependentAddPtr<InitialShapeSet>>keyPointer;JSProtoKeykey=GetInitialShapeProtoKey(protoRoot,cx);if(key!=JSProto_LIMIT){keyPointer.emplace(MakeDependentAddPtr(cx,table,Lookup(clasp,Lookup::ShapeProto(key),nfixed,objectFlags)));if(keyPointer.ref()){shape=keyPointer.ref()->shape;MOZ_ASSERT(shape);}else{insertKey=true;}}if(!shape){StackBaseShapebase(cx,clasp,objectFlags);Rooted<UnownedBaseShape*>nbase(cx,BaseShape::getUnowned(cx,base));if(!nbase)returnnullptr;shape=EmptyShape::new_(cx,nbase,nfixed);if(!shape)returnnullptr;}Lookup::ShapeProtoshapeProto(protoRoot);Lookuplookup(clasp,shapeProto,nfixed,objectFlags);if(!protoPointer.add(cx,table,lookup,InitialShapeEntry(shape,shapeProto)))returnnullptr;// Also add an entry based on the JSProtoKey, if needed.if(insertKey){Lookup::ShapeProtoshapeProto(key);Lookuplookup(clasp,shapeProto,nfixed,objectFlags);if(!keyPointer->add(cx,table,lookup,InitialShapeEntry(shape,shapeProto)))returnnullptr;}returnshape;}/* static */Shape*EmptyShape::getInitialShape(JSContext*cx,constClass*clasp,TaggedProtoproto,AllocKindkind,uint32_tobjectFlags){returngetInitialShape(cx,clasp,proto,GetGCKindSlots(kind,clasp),objectFlags);}voidNewObjectCache::invalidateEntriesForShape(JSContext*cx,HandleShapeshape,HandleObjectproto){constClass*clasp=shape->getObjectClass();gc::AllocKindkind=gc::GetGCObjectKind(shape->numFixedSlots());if(CanBeFinalizedInBackground(kind,clasp))kind=GetBackgroundAllocKind(kind);RootedObjectGroupgroup(cx,ObjectGroup::defaultNewGroup(cx,clasp,TaggedProto(proto)));if(!group){purge();cx->recoverFromOutOfMemory();return;}EntryIndexentry;for(CompartmentsInZoneItercomp(shape->zone());!comp.done();comp.next()){if(GlobalObject*global=comp->unsafeUnbarrieredMaybeGlobal()){if(lookupGlobal(clasp,global,kind,&entry))PodZero(&entries[entry]);}}if(!proto->is<GlobalObject>()&&lookupProto(clasp,proto,kind,&entry))PodZero(&entries[entry]);if(lookupGroup(group,kind,&entry))PodZero(&entries[entry]);}/* static */voidEmptyShape::insertInitialShape(JSContext*cx,HandleShapeshape,HandleObjectproto){usingLookup=InitialShapeEntry::Lookup;Lookuplookup(shape->getObjectClass(),Lookup::ShapeProto(TaggedProto(proto)),shape->numFixedSlots(),shape->getObjectFlags());InitialShapeSet::Ptrp=cx->zone()->initialShapes().lookup(lookup);MOZ_ASSERT(p);InitialShapeEntry&entry=const_cast<InitialShapeEntry&>(*p);// The metadata callback can end up causing redundant changes of the initial shape.if(entry.shape==shape)return;// The new shape had better be rooted at the old one.#ifdef DEBUGShape*nshape=shape;while(!nshape->isEmptyShape())nshape=nshape->previous();MOZ_ASSERT(nshape==entry.shape);#endifentry.shape=ReadBarrieredShape(shape);// For certain prototypes -- namely, those of various builtin classes,// keyed by JSProtoKey |key| -- there are two entries: one for a lookup// via |proto|, and one for a lookup via |key|. If this is such a// prototype, also update the alternate |key|-keyed shape.JSProtoKeykey=GetInitialShapeProtoKey(TaggedProto(proto),cx);if(key!=JSProto_LIMIT){Lookuplookup(shape->getObjectClass(),Lookup::ShapeProto(key),shape->numFixedSlots(),shape->getObjectFlags());if(InitialShapeSet::Ptrp=cx->zone()->initialShapes().lookup(lookup)){InitialShapeEntry&entry=const_cast<InitialShapeEntry&>(*p);if(entry.shape!=shape)entry.shape=ReadBarrieredShape(shape);}}/* * This affects the shape that will be produced by the various NewObject * methods, so clear any cache entry referring to the old shape. This is * not required for correctness: the NewObject must always check for a * nativeEmpty() result and generate the appropriate properties if found. * Clearing the cache entry avoids this duplicate regeneration. * * Clearing is not necessary when this context is running off * thread, as it will not use the new object cache for allocations. */if(!cx->helperThread())cx->caches().newObjectCache.invalidateEntriesForShape(cx,shape,proto);}voidZone::fixupInitialShapeTable(){if(!initialShapes().initialized())return;for(InitialShapeSet::Enume(initialShapes());!e.empty();e.popFront()){// The shape may have been moved, but we can update that in place.Shape*shape=e.front().shape.unbarrieredGet();if(IsForwarded(shape)){shape=Forwarded(shape);e.mutableFront().shape.set(shape);}shape->updateBaseShapeAfterMovingGC();// If the prototype has moved we have to rekey the entry.InitialShapeEntryentry=e.front();if(entry.proto.proto().isObject()&&IsForwarded(entry.proto.proto().toObject())){entry.proto.setProto(TaggedProto(Forwarded(entry.proto.proto().toObject())));usingLookup=InitialShapeEntry::Lookup;Lookuprelookup(shape->getObjectClass(),Lookup::ShapeProto(entry.proto),shape->numFixedSlots(),shape->getObjectFlags());e.rekeyFront(relookup,entry);}}}voidAutoRooterGetterSetter::Inner::trace(JSTracer*trc){if((attrs&JSPROP_GETTER)&&*pgetter)TraceRoot(trc,(JSObject**)pgetter,"AutoRooterGetterSetter getter");if((attrs&JSPROP_SETTER)&&*psetter)TraceRoot(trc,(JSObject**)psetter,"AutoRooterGetterSetter setter");}JS::ubi::Node::SizeJS::ubi::Concrete<js::Shape>::size(mozilla::MallocSizeOfmallocSizeOf)const{Sizesize=js::gc::Arena::thingSize(get().asTenured().getAllocKind());AutoCheckCannotGCnogc;if(ShapeTable*table=get().maybeTable(nogc))size+=table->sizeOfIncludingThis(mallocSizeOf);if(!get().inDictionary()&&get().kids.isHash())size+=get().kids.toHash()->sizeOfIncludingThis(mallocSizeOf);returnsize;}JS::ubi::Node::SizeJS::ubi::Concrete<js::BaseShape>::size(mozilla::MallocSizeOfmallocSizeOf)const{returnjs::gc::Arena::thingSize(get().asTenured().getAllocKind());}voidPropertyResult::trace(JSTracer*trc){if(isNativeProperty())TraceRoot(trc,&shape_,"PropertyResult::shape_");}